/*
    *@brief :调用该函数，内核将一个给定的文件映射到进程地址空间中的一块内存区域中

    *@param[addr]   :指定映射到内存区域的起始地址。通常将其设置为 NULL

    *@param[length] :参数 length 指定映射长度， 表示将文件中的多大部分映射到内存区域中，以字节为单位，
                    -譬如length=1024 * 4，表示将文件的 4K 字节大小映射到内存区域中。

    *@param[prot]   : prot 指定了映射区的保护要求，可取值如下：
                    -PROT_EXEC： 映射区可执行；
                    -PROT_READ： 映射区可读；
                    -PROT_WRITE： 映射区可写；
                    -PROT_NONE： 映射区不可访问。

                    - prot 指定为为 PROT_NONE，也可将其设置为 PROT_EXEC、 PROT_READ、 PROT_WRITE 中一个或多个（通过按位或运算符任意组合）。
                     对指定映射区的保护要求不能超过文件 open()时的访问权限，
                     譬如，文件是以只读权限方式打开的，那么对映射区的不能指定为 PROT_WRITE。

    *@param[flags]  : flags 可影响映射区的多种属性， 参数 flags 必须要指定以下两种标志之一：
                    - MAP_SHARED： 此标志指定当对映射区写入数据时，数据会写入到文件中，也就是会将写入到映射区中的数据更新到文件中，并且允许其它进程共享。
                    - MAP_PRIVATE： 此标志指定当对映射区写入数据时，会创建映射文件的一个私人副本（copy-onwrite），对映射区的任何操作都不会更新到文件中，仅仅只是对文件副本进行读写。除此之外，还可将以下标志中的 0 个或多个组合到参数 flags 中，通过按位或运算符进行组合：
                    - MAP_FIXED： 在未指定该标志的情况下，如果参数 addr 不等于 NULL，表示由调用者自己指定映射区的起始地址，但这只是一种建议、而并非强制，所以内核并不会保证使用参数 addr 指定的值作为映射区的起始地址；如果指定了 MAP_FIXED 标志，则表示要求必须使用参数 addr 指定的值作为起始地址，如果使用指定值无法成功建立映射时，则放弃！通常，不建议使用此标志，因为这不利于移植。
                    - MAP_ANONYMOUS： 建立匿名映射， 此时会忽略参数 fd 和 offset，不涉及文件，而且映射区域无法和其它进程共享。
                    - MAP_ANON： 与 MAP_ANONYMOUS 标志同义，不建议使用。
                    - MAP_DENYWRITE： 该标志被忽略。
                    - MAP_EXECUTABLE： 该标志被忽略。
                    - MAP_FILE： 兼容性标志，已被忽略。
                    - MAP_LOCKED： 对映射区域进行上锁。

                    通常情况下，参数 flags 中只指定了 MAP_SHARED。

    *@param[fd]     :文件描述符，指定要映射到内存区域中的文件。
    *@param[offset] :文件映射的偏移量，通常将其设置为 0

    *@return        ：成功情况下，函数的返回值便是映射区的起始地址；发生错误时，返回(void *)-1，
                    -通常使用MAP_FAILED 来表示， 并且会设置 errno 来指示错误原因
*/

/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

系统调用 mprotect()可以更改一个现有映射区的保护要求
int mprotect(void *addr, size_t len, int prot);

*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
    //定义目标文件和源文件描述符
    int srcfd, dstfd;

    //定义源文件起始地址
    void *srcaddr;

    //定义目标文件起始地址
    void *dstaddr;
    int ret;
    struct stat sbuf;

    //判断输入是否有2个参数
    if (3 != argc)
    {
        fprintf(stderr, "usage: %s <srcfile> <dstfile>\n", argv[0]);
        exit(-1);
    }

    /* 打开源文件 */
    srcfd = open(argv[1], O_RDONLY);
    if (-1 == srcfd)
    {
        perror("open error");
        ret = -1;
        goto out1;
    }

    /* 打开目标文件 */
    dstfd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664);
    if (-1 == dstfd)
    {
        perror("open error");
        ret = -1;
        goto out1;
    }

    /* 获取源文件的大小 */
    fstat(srcfd, &sbuf);

    /* 设置目标文件的大小 */
    ftruncate(dstfd, sbuf.st_size);

    /* 将源文件映射到内存区域中 */
    srcaddr = mmap(NULL, sbuf.st_size, PROT_READ, MAP_SHARED, srcfd, 0);
    if (MAP_FAILED == srcaddr)
    {
        perror("mmap error");
        ret = -1;
        goto out3;
    }

    /* 将目标文件映射到内存区域中 */
    dstaddr = mmap(NULL, sbuf.st_size,
                   PROT_WRITE, MAP_SHARED, dstfd, 0);
    if (MAP_FAILED == dstaddr)
    {
        perror("mmap error");
        ret = -1;
        goto out4;
    }

    /* 将源文件中的内容复制到目标文件中 */
    memcpy(dstaddr, srcaddr, sbuf.st_size);
    ret = 0;
    goto out4;

    /* 程序退出前清理工作 */
out4:
    /* 解除目标文件映射 */
    munmap(dstaddr, sbuf.st_size);
    goto out3;
out3:
    /* 解除源文件映射 */
    munmap(srcaddr, sbuf.st_size);
    goto out2;
out2:
    /* 关闭目标文件 */
    close(dstfd);
    goto out1;
out1:
    /* 关闭源文件并退出 */
    close(srcfd);
    exit(ret);
}